//
//  APIHeartRateImp+Repo.swift
//  APIHeartRate
//
//  Created by Tek on 2023/3/13.
//

import Foundation
import CoreBluetooth

extension APIHeartRateImp: HRRepository {
    
    func startScan(timeOut: Double) {
        loggerInternal.i(info: "startScan(timeOut: \(timeOut))")
        central.scanWithDuration(timeOut) { [weak self] newDiscoveries in
            self?.observers.forEach({ _, delegateApi in
                delegateApi.didDiscoveryWith(devices: newDiscoveries)
            })
        } completionHandler: { [weak self] result, scanError in
            self?.observers.forEach({ _, delegateApi in
                guard let res = result else {
                    let err = scanError ?? BleConnectError.scanFailed
                    delegateApi.bleConnectError(error: err, device: nil)
                    return
                }
                delegateApi.didFinishDiscoveryWith(devices: res) })
        }
    }
    
    func stopScan() {
        loggerInternal.i(info: "stopScan()")
        central.endScan()
    }
    
    func getMacAddress() {
        loggerInternal.i(info: "getMacAddress()")
        writeData(Data([HRServiceConst.cmdMac]))
    }

    func getDeviceBattery() {
        loggerInternal.i(info: "getDeviceBattery()")
        readData(.battery)
    }

    func getDeviceManufacturer() {
        loggerInternal.i(info: "getDeviceManufacturer()")
        readData(.manufacturer)
    }

    func getDeviceModelNum() {
        loggerInternal.i(info: "getDeviceModelNum()")
        readData(.model)
    }

    func getDeviceHardware() {
        loggerInternal.i(info: "getDeviceHardware()")
        readData(.hardware)
    }

    func getDeviceFirmware() {
        loggerInternal.i(info: "getDeviceFirmware()")
        readData(.firmware)
    }

    func getDeviceSoftware() {
        loggerInternal.i(info: "getDeviceSoftware()")
        readData(.software)
    }

    func getDeviceSystemID() {
        loggerInternal.i(info: "getDeviceSystemID()")
        readData(.sysid)
    }

    func getHearRate() {
        // readData(.rate)
    }

    func setDeviceThreshold(min: UInt8,max: UInt8 = 0) {
        loggerInternal.i(info: "setDeviceThreshold(min: \(min),max:\(max))")
        writeData(Data([HRServiceConst.cmdVibrate, max, min]))
    }

    func getDeviceSerial() {
        loggerInternal.i(info: "getDeviceSerial()")
        writeData(Data([HRServiceConst.cmdSerial]))
    }

    func getDeviceStepFrequency() {
        loggerInternal.i(info: "getDeviceStepFrequency()")
        writeData(Data([HRServiceConst.cmdStep]))
    }

    func getRealTimeOxygen() {
        loggerInternal.i(info: "getRealTimeOxygen()")
        writeData(Data([HRServiceConst.cmdOxygen]))
    }
    
    func startOTAWith(file: Data) {
        guard remotePeripheral != nil else {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.devieNotConnected) })
            return
        }
        HROTAManager.share.startOtaWithResouce(data: file)
    }
    
    func queryBatteryStatus() {
        loggerInternal.i(info: "queryBatteryStatus()")
        writeData(Data([HRServiceConst.cmdBattery]))
    }

    func queryDeviceMode() {
        loggerInternal.i(info: "queryDeviceMode()")
        writeData(Data([HRServiceConst.cmdMode]))
    }
    
    func queryCursorInfo() {
        loggerInternal.i(info: "queryCursorInfo()")
        writeData(Data([0x16]))
    }
    func switchDeviceMode(mode: DeviceMode, isReal: Bool) {
        loggerInternal.i(info: "switchDeviceMode(mode: \(mode), isReal:\(isReal))")
        writeData(Data([HRServiceConst.cmdSetMode, mode == .daily ? 0 : 1]))
        if (mode == .sport) {
            writeData(Data(HRServiceConst.mockRate(on: !isReal)))
        }
    }
    func requestCurrentTime() {
        loggerInternal.i(info: "requestCurrentTime()")
        writeData(Data([HRServiceConst.cmdCurrent]))
    }
    func keepSportAlive() {
        loggerInternal.i(info: "keepSportAlive()")
        writeData(Data([HRServiceConst.cmdKeepLive]))
    }
    func qureySportModeDetail() {
        loggerInternal.i(info: "qureySportModeDetail()")
        writeData(Data([HRServiceConst.cmdModeDetail]))
    }
    func syncTime() {
        loggerInternal.i(info: "syncTime()")
        let cmp = Calendar.current.dateComponents(in: .current, from: Date())
        var year = cmp.year ?? 0
        if year > 1970 {
            year -= 1970
        }
        let mon = UInt8(cmp.month ?? 0)
        let day = UInt8(cmp.day ?? 0)
        let hour = UInt8(cmp.hour ?? 0)
        let min = UInt8(cmp.minute ?? 0)
        let second = UInt8(cmp.second ?? 0)
        writeData(Data([HRServiceConst.cmdSyncTime, UInt8(year), mon, day, hour, min, second]))
    }
    
    func readFirmawareInternal() {
        loggerInternal.i(info: "readFirmawareInternal()")
        readData(.firmware)
    }
    
    func resetFactory() {
        loggerInternal.i(info: "resetFactory()")
        isClickReset = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 1){[weak self] in
            guard let this = self else {return }
            if this.isClickReset {
                this.isClickReset = false
                this.observers.forEach({ _, delegateApi in
                    delegateApi.armBandResetFactory(isTimeOut:true) })
            }
        }
        writeData(Data([HRServiceConst.cmdReset]))
    }
    
    func forseSleepOut() {
        loggerInternal.i(info: "forseSleepOut()")
        writeData(Data([HRServiceConst.cmdAlgoOut]))
    }
    
    func setDoNotDisturbTime(enabled: Bool,startHour: UInt8, startMin: UInt8, endHour: UInt8, endMin: UInt8) {
        loggerInternal.i(info: "setDoNotDisturbTime(timeOut: \(enabled), \(startHour)\(startMin):\(endHour)\(endMin)")
        writeData(Data([HRServiceConst.cmdDoNotTurb, UInt8(enabled ? 1 : 0), startHour, startMin, endHour, endMin]))
    }

    func readCharValue(charUUID: String) {
        guard let remote = remotePeripheral else {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.devieNotConnected) })
            return
        }
        let uid = CBUUID(string: charUUID)
        let result = central.readChar(uuid: uid, toRemotePeer: remote)
        if !result {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.serviceNotFound) })
        }
    }

    func setCharNotify(charUUID: String, enabled: Bool) {
        guard let remote = remotePeripheral else {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.devieNotConnected) })
            return
        }
        let uid = CBUUID(string: charUUID)
        let result = central.setCharNotify(uuid: uid, enable: enabled, toRemotePeer: remote)
        if !result {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.serviceNotFound) })
        }
    }

    func writeChar(charUUID: String, data: Data) {
        guard let remote = remotePeripheral else {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.devieNotConnected) })
            return
        }
        let uid = CBUUID(string: charUUID)
        let result = central.writeChar(uuid: uid, data, toRemotePeer: remote)
        if !result {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.serviceNotFound) })
        }
    }

    private func readData(_ type: BleDevice.ReadType) {
        loggerInternal.d(info: "readData(type: \(type))")
        guard let remote = remotePeripheral else {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.devieNotConnected) })
            return
        }
        let result = central.readData(type, toRemotePeer: remote)
        if !result {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.serviceNotFound) })
        }
    }

    internal func writeData(_ data: Data) {
        loggerInternal.d(info: "writeData(data: \(dataToHex(dataBuffer: data)))")
        guard let remote = remotePeripheral else {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.devieNotConnected) })
            return
        }
        let result = central.sendData(data, toRemotePeer: remote)
        if !result {
            observers.forEach({ _, delegateApi in
                delegateApi.bleCommonError(error: BleCommonError.serviceNotFound) })
        }
    }
    
    func getSummaryHeartRate(startDateStamp: Double, endDateStamp: Double) {
        fetchDayData(type: .heartRate, startDateStamp: startDateStamp, endDateStamp: endDateStamp)
    }
    
    func getSummarySteps(startDateStamp: Double, endDateStamp: Double) {
        fetchDayData(type: .steps, startDateStamp: startDateStamp, endDateStamp: endDateStamp)
    }
    
    func getSummaryBloodOxygen(startDateStamp: Double, endDateStamp: Double) {
        fetchDayData(type: .oxygen, startDateStamp: startDateStamp, endDateStamp: endDateStamp)
    }

    func getSummaryPressureIndex(startDateStamp: Double, endDateStamp: Double) {
        fetchDayData(type: .pressure, startDateStamp: startDateStamp, endDateStamp: endDateStamp)
    }
    
    func getSummarySleep(startDateStamp: Double, endDateStamp: Double) {
        fetchDayData(type: .sleep, startDateStamp: startDateStamp, endDateStamp: endDateStamp)
    }
    
    func getSummaryCalorie(startDateStamp: Double, endDateStamp: Double) {
        fetchDayData(type: .calorie, startDateStamp: startDateStamp, endDateStamp: endDateStamp)
    }
    
    func getExerciseData(startDateStamp: Double, endDateStamp: Double) {
        fetchDayData(type: .excercise, startDateStamp: startDateStamp, endDateStamp: endDateStamp)
    }
    
    
    func getMotionData(pageSize: Int, sportMode: Bool) {
        loggerInternal.i(info: "getMotionData(pageSize: \(pageSize), sportMode: \(sportMode))")
        dataMgt.clearData()
        dataMgt.isSport = sportMode
        dataMgt.dataType = sportMode ? .motionSport : .motionDaily
        let truePage = pageSize / (sportMode ? 60 : 1)
        var data = Data(count: MemoryLayout<Int32>.size)
        withUnsafeBytes(of: Int32(truePage).littleEndian) { bufferPointer in
            data.withUnsafeMutableBytes { dataPointer in
                dataPointer.copyMemory(from: bufferPointer)
            }
        }
        let cmd = sportMode ? HRServiceConst.cmdDataSport : HRServiceConst.cmdDataDaily
        pageSizeReq = truePage
        writeData(Data([cmd] + data))
    }
    
    func updateSeq(sportMode: Bool) {
        loggerInternal.i(info: "updateSeq(sportMode: \(sportMode)")
        var data = Data(count: MemoryLayout<Int32>.size)
        withUnsafeBytes(of: Int32(pageSizeReq).littleEndian) { bufferPointer in
            data.withUnsafeMutableBytes { dataPointer in
                dataPointer.copyMemory(from: bufferPointer)
            }
        }
        let cmd = sportMode ? HRServiceConst.cmdDataSportCheck : HRServiceConst.cmdDataDailyCheck
        writeData(Data([cmd] + data))
    }
    
    func summaryQuery(cmd: UInt8, startDateStamp: Double, endDateStamp: Double) {
        loggerInternal.i(info: "summaryQuery(cmd: \(cmd), startDateStamp: \(startDateStamp), endDateStamp: \(endDateStamp))")
        dataMgt.clearData()
        var a = Int32(startDateStamp)
        var b = Int32(endDateStamp)
        let myIntAData = Data(bytes: &a, count: MemoryLayout.size(ofValue: a))
        let myIntBData = Data(bytes: &b, count: MemoryLayout.size(ofValue: b))
        var sendD = Data([cmd])
        sendD.append(Data(myIntAData))
        sendD.append(Data(myIntBData))
        writeData(sendD)
    }
    
    private func fetchDayData(type: FetchDataType, startDateStamp: Double, endDateStamp: Double) {
        loggerInternal.i(info: "fetchDayData(type: \(type),startDateStamp: \(startDateStamp), endDateStamp: \(endDateStamp))")
        
        dataType = type
        dataMgt.dataType = type
        switch (type) {
        case .heartRate, .steps, .calorie, .pressure, .oxygen:
            summaryQuery(cmd: HRServiceConst.cmdDataDay, startDateStamp: startDateStamp, endDateStamp: endDateStamp)
        case .sleep:
            summaryQuery(cmd: HRServiceConst.cmdDataSleep, startDateStamp: startDateStamp - 24 * 60 * 60, endDateStamp: endDateStamp - 24 * 60 * 60)
        case .excercise:
            summaryQuery(cmd: HRServiceConst.cmdDataExcercise, startDateStamp: startDateStamp, endDateStamp: endDateStamp)
        default:
            break
        }
    }

    func ackToLength(sig: UInt8) {
        loggerInternal.d(info: "ackToLength(sig: \(sig))")
        writeData(Data([sig]))
    }
    
    func lightSwitch(on: Bool) {
        loggerInternal.i(info: "lightSwitch(on: \(on))")
        writeData(HRServiceConst.cmdLight(on: on))
    }
    
    func dynamicHeartRate(on: Bool) {
        loggerInternal.i(info: "dynamicHeartRate(on: \(on))")
        writeData(HRServiceConst.cmdDynamicHeartRate(on: on))
    }
    
    func queryHeartRateMode() {
        loggerInternal.i(info: "queryHeartRateMode()")
        writeData(Data([HRServiceConst.queryHeartRateMode]))
    }
    
    func gymnasticMode(on: Bool) {
        loggerInternal.i(info: "gymnasticMode(on: \(on))")
        writeData(HRServiceConst.cmdGymnastic(on: on))
    }
    
    func enableRawDataLog(on: Bool) {
        loggerInternal.i(info: "enableRawDataLog(on: \(on))")
        self.showRawData = on
    }
    
    func queryDeviceInformation() {
        loggerInternal.i(info: "queryDeviceInformation()")
        writeData(Data([HRServiceConst.cmdQueryInfo]))
    }
    
    func switchScenario(type: ScenarioType) {
        loggerInternal.i(info: "switchScenario(type: \(type), isLegacyVersion:\(isLegacyVersion)")
        if (isLegacyVersion) {
            handleLegacyScenario(type: type)
        } else {
            writeData(HRServiceConst.scenarioCmd(type: type))
        }
    }
    
    private func handleLegacyScenario(type: ScenarioType) {
        weak var weakSelf = self
        switch type {
        case .daily:
            self.dynamicHeartRate(on: false)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2){
                weakSelf?.switchDeviceMode(mode: .daily, isReal: false)
            }
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4){
                weakSelf?.lightSwitch(on: false)
            }
        case .lesson:
            self.switchDeviceMode(mode: .sport, isReal: false)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2){
                weakSelf?.dynamicHeartRate(on: true)
            }
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4){
                weakSelf?.lightSwitch(on: true)
            }
        case .quickWalk:
            self.switchDeviceMode(mode: .sport, isReal: false)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2){
                weakSelf?.dynamicHeartRate(on: false)
            }
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4){
                weakSelf?.lightSwitch(on: true)
            }
        }
    }
    
}
